#include "Reservation.h"
#include "Bill.h"
#include "Room.h"
#include "Price.h"
#include "Hotel.h"

/*
==================
Reservation::Reservation

Read the reservation from file.
==================
*/
Reservation::Reservation(ifstream &file, int arrDate) : Num_element(arrDate) {
	room = NULL;
	
	bills = new List(FIFO);
	arrivalDate = arrDate;
	
	// Read Departure Date
	file >> departureDate;
	file.ignore();

	// Read the length of the stay
	file >> lengthOfStay;
	file.ignore();
	
	/* Make sure the difference on file matches that calculated
	 * by the Timer-object. If there is a mismatch, the
	 * value from file is used.
	 */
	int dateDiff = Singletons::timer->forskjell_datoer(arrivalDate,
														departureDate);
	if (lengthOfStay != dateDiff) {
		stringstream ss;
		ss << "Reservation(), length of stay mismatch: file(";
		ss << lengthOfStay << ") date(" << dateDiff;
		ss << ")\n\tArrival: " << arrivalDate;
		ss << "\n\tDeparture: " << departureDate;
		Error(ss.str());
	}

	// Read pricing info for each day of the stay
	dailyPrices = new float[lengthOfStay];
	for (unsigned i = 0; i < lengthOfStay; i++) {
		file >> dailyPrices[i];
		file.ignore();
	}

	// Read the bed status
	int tmpBedStatus;
	file >> tmpBedStatus;
	bedStatus = (BedStatus)tmpBedStatus;
	file.ignore();
	
	// Read check-in status
	file >> checkedIn;
	file.ignore();

	// Read number of residents
	file >> numResidents;
	file.ignore();

	// Read the names of the residents
	residentNames = new string[numResidents];
	for (unsigned i=0; i<numResidents; i++) {
		getline(file, residentNames[i]);
	}

	// Read the number of bills
	int numBills = 0;
	file >> numBills;
	file.ignore();

	// Read in the bills
	for (unsigned i = 0; i < numBills; i++) {
		Bill *bill = new Bill(file);
		bills->add(bill);
	}
	
}

/*
==================
Reservation::Reservation

The reservation-data is read in the method Hotel::bookRoom().
==================
*/
Reservation::Reservation(Date arrDate) : Num_element(arrDate) {
	room = NULL;
	arrivalDate = arrDate;
	bills = new List(FIFO);
	
	departureDate 	= 0;
	lengthOfStay	= 0;
	dailyPrices 	= NULL;
	bedStatus		= BED_UNAVAILABLE;
	checkedIn		= false;
	numResidents	= 0;
	residentNames 	= NULL;
}

/*
==================
Reservation::~Reservation
==================
*/
Reservation::~Reservation() {
	if (bills) {
		delete	 bills;
	}
	
	if (dailyPrices) {
		delete[] dailyPrices;
	}
	
	if (residentNames) {
		delete[] residentNames;	
	}
}

/*
==================
Reservation::display

Display information related to the reservation.
==================
*/
void Reservation::display() {
	/* Print the room number */
	printf("\n\tRoom number:\n\t\t%i\n", room->getRoomNumber());

	/* Print resident names */
	printf("\tResidents:\n");
	
	for (unsigned i=0; i<numResidents; i++) {
		printf("\t\t%s\n", residentNames[i].c_str());
	}
	
	/* Print relevant dates */
	printf("\tArrival date:\n");
	printf("\t\t%s\n", dateToString(arrivalDate).c_str());
	printf("\tDeparture date:\n");
	printf("\t\t%s\n", dateToString(departureDate).c_str());
	
	printf("\n");
}

/*
==================
Reservation::WriteToFile

Write relevant data to file.
==================
*/
void Reservation::WriteToFile(ofstream &file) {
	file << arrivalDate;
	file << endl;
	
	file << departureDate;
	file << endl;
	
	file << lengthOfStay;
	file << endl;
	
	for (unsigned i = 0; i < lengthOfStay; i++) {
		file << dailyPrices[i];
		file << endl;
	}
	
	file << (int)bedStatus;
	file << endl;
	
	file << checkedIn;
	file << endl;
	
	file << numResidents;
	file << endl;
	
	for (unsigned i = 0; i < numResidents; i++) {
		file << residentNames[i];
		file << endl;
	}
	
	file << bills->no_of_elements();
	file << endl;
	
	for (unsigned i = 0; i < bills->no_of_elements(); i++) {
		Bill *bill = (Bill*)bills->remove();
		bills->add(bill);
		
		bill->WriteToFile(file);
	}
}

/*
==================
Reservation::IncludesDate

Is "date" one of the days the guest is staying?
==================
*/
bool Reservation::IncludesDate(Date date) {
	return (arrivalDate <= date && departureDate > date);
}

/*
==================
Reservation::isCheckedIn
==================
*/
bool Reservation::isCheckedIn() {
	return checkedIn;
}

/*
==================
Reservation::getCustomerName

The first registered resident in a reservation is the "booker", and
his or her name is returned.
==================
*/
string* Reservation::getCustomerName() {
	if (numResidents) {
		
		return residentNames;
	}
	
	return NULL;
}

/*
==================
Reservation::numOfCustomers
==================
*/
int Reservation::numOfCustomers() {

	return numResidents;
}

/*
==================
Reservation::printBills
==================
*/
void Reservation::printBills() {

	// If there is any bills
	if(bills) {
		int numOfBills = bills->no_of_elements();
		float totalSum = 0.0f;
		Bill* bill;

		// Going though all bills in the list and getting the sum
		for(unsigned i = 0; i < numOfBills; i++) {
			bill = (Bill*)bills->remove();
			bills->add(bill);

			totalSum += bill->GetSum();
		}

		// Displays the list
		if (numOfBills) {
			printf("\n\n\tFacilities:\n");
			bills->display_list();
		}
		
		printf("\n\n\tTotal sum: %0.2f\n", totalSum);
	}
}

/*
==================
Reservation::addBill
==================
*/
void Reservation::addBill(string desc, float sum) {
	Bill *bill = new Bill(false);
	bill->setData(desc, sum);
	bills->add(bill);
}

/*
==================
Reservation::setArrivalDate

The new arrival date MUST be before the departure date.
==================
*/
void Reservation::setArrivalDate(Date arr) {
	if (DateVerify::Verify(arr) == DateVerify::ERR_NONE) {
		if (arr < departureDate) {
			// Find the difference between the new arrival and the existing departure
			int newLen = Singletons::timer->forskjell_datoer(arr, departureDate);

			/* updateDailyPricesOnArrivalChange REQUIRES the "old" arrivalDate to
			 * remain unchanged until it has calculated the new prices.
			 */
			updateDailyPricesOnArrivalChange(newLen, arr);

			// Now we can update the old values
			arrivalDate = arr;
			lengthOfStay = newLen;
		} else {
			Error("Reservation::setArrivalDate, arrival MUST be < departure");
		}
	} else {
		Error("Resrvation::setArrivalDate, invalid date given");
	}
}


/*
==================
Reservation::setDepartureDate

The new departure date MUST come after the arrival date.
==================
*/
void Reservation::setDepartureDate(Date dep) {
	if (DateVerify::Verify(dep) == DateVerify::ERR_NONE) {
		if (dep > arrivalDate) {
			/* Set the date and length of stay */
			departureDate = dep;
			int newLen = Singletons::timer->forskjell_datoer(arrivalDate, departureDate);

			// Update the daily prices
			udpateDailyPricesOnDepartureChange(newLen);
			
			lengthOfStay = newLen;
		} else {
			Error("Reservation::setDepartureDate, departure MUST be > arrival");
		}
	} else {
		Error("Reservation::setDepartureDate, invalid date given.");
	}
}

/*
==================
Reservation::updateDailyPrices

The daily prices are updated initially by using the 
"updateDailyPricesOnDepartureChange()"-method, as it's functionality
does not nessecarily rely on "old" values existing.
==================
*/
void Reservation::updateDailyPrices(int newLen) {
	udpateDailyPricesOnDepartureChange(newLen);
}

/*
==================
Reservation::updateDailyPricesOnDepartureChange

As the method name implies, the daily pricse are updated. The method
can only expand or shrink the "dailyPrices"-array, and can as such be
used for setting the initial dailyPrices or updating on departure change.
==================
*/
void Reservation::udpateDailyPricesOnDepartureChange(int newLen) {
	/* Keep old prices */
	vector<float> old;
	
	if (dailyPrices) {
		for (unsigned i = 0; i < lengthOfStay; i++) {
			old.push_back(dailyPrices[i]);
		}
		delete[] dailyPrices;
	}
	
	/* Assign new (and keep old!) prices */
	dailyPrices = new float[newLen];
	Price price(Singletons::hotel->getFileName());

	// "Iteration" date
	Date date = arrivalDate;
	
	// Going through all the days for the stay
	for (unsigned i = 0; i < newLen; i++) {

		// As long it is the same date
		if (i < old.size()) {
			dailyPrices[i] = old[i];

		// There is a new date
		} else {
			dailyPrices[i] = price.getPrice(date, room->GetType());
		}
		
		date = Singletons::timer->nestedato(date);
	}
}

/*
==================
Reservation::updateDailyPricesOnArrivalChange

This method expects the member "arrivalDate" to NOT have been altered before
call.

When the arrival date is changed, the algorithm required to set
the new prices while still including existing values is very different than
when the departure date has been altered. 
==================
*/
void Reservation::updateDailyPricesOnArrivalChange(int newLen, Date newArr) {
	/* Backup old prices */
	vector<float> old;

	if (dailyPrices) {
		for (unsigned i = 0; i < lengthOfStay; i++) {
			old.push_back(dailyPrices[i]);
		}
		delete[] dailyPrices;
	}

	/* Assign new (and keep old!) prices */
	dailyPrices = new float[newLen];
	Price price(Singletons::hotel->getFileName());

	// "Iteration" date
	Date date = newArr;

	// Find the difference in days. If "diff < 0", we must create new
	// values for "dailyPrices", as "newArr < arrivalDate".
	int diff = newArr - arrivalDate;
	int i = 0;

	// Calculate new daily prices
	while (diff < 0) {
		dailyPrices[i++] = price.getPrice(date, room->GetType());

		date = Singletons::timer->nestedato(date);
		diff++; 
	}

	/* If "i == 0", the new arrival date is in the future.
	 * As such, "diff" has a positive (or nil) value, and we know that
	 * "dailyPrices[0]" should be "old[diff]" in either case.
	 */
	while (i < newLen) {
		dailyPrices[i++] = old[diff++];
	}
}

/*
==================
Reservation::readResidents
==================
*/
void Reservation::readResidents() {
	if (residentNames) {
		delete residentNames;
		numResidents = 0;
	}
	
	vector<string> tmpNames;
	string tmpStr;
	
	/* Need at least one name, input is stopped
	 * by pressing enter on any but the first query.
	 */
	while ((!numResidents || tmpStr.length()) && numResidents < room->getBedCount()) {
		stringstream ss;
		ss << "Name of resident " << numResidents+1;
		tmpStr = ReadString(ss.str(), 0);
		
		if (tmpStr.length()) {
			tmpNames.push_back(tmpStr);
			numResidents++;
		}
	}
	
	/* Copy the temporary names into "residentNames" */
	residentNames = new string[numResidents];
	for (unsigned i = 0; i < numResidents; i++) {
		residentNames[i] = tmpNames[i];
	}
}

/*
==================
Reservation::readAdditionalResidents

Called when the resident checks in, in case he/she brought 
additional guests.
==================
*/
void Reservation::readAdditionalResidents() {
	/* Print the current residents */
	printf("\tRoom %i\n\tCurrent residents:\n", room->getRoomNumber());
	for (unsigned i = 0; i < numResidents; i++) {
		printf("\t\t%s\n", residentNames[i].c_str());
	};

	/* Print the number of free beds, return if 0 */
	int remainding = room->getBedCount() - numResidents;
	printf("\n\tThere are %i free beds.\n", remainding);
	if (remainding <= 0) {
		return;
	}

	/* Read additional residents */
	vector<string> newResidents;
	string tmpStr;

	do {
		tmpStr = ReadString("New resident", 0);
		if (tmpStr.length()) {
			newResidents.push_back(tmpStr);
		}
	} while (newResidents.size() < remainding && tmpStr.length());

	if (newResidents.size()) {
		/* Put the existing names in a temporary vector */
		vector<string> oldResidents;
		for (unsigned i = 0; i < numResidents; i++) {
			oldResidents.push_back(residentNames[i]);
		}

		/* Join "oldResidents" and "newResidents" */
		delete[] residentNames;
		residentNames = new string[numResidents + newResidents.size()];

		for (unsigned i = 0; i < oldResidents.size(); i++) {
			residentNames[i] = oldResidents[i];
		}

		for (unsigned i = 0; i < newResidents.size(); i++) {
			residentNames[i+numResidents] = newResidents[i];
		}

		// Adding the new residents
		numResidents += newResidents.size();
	}
}








